Implementation of Drawing Roughness Marking Function in MxCAD
Preface
Surface roughness symbols are important marking symbols in mechanical drawing, used to indicate the micro-roughness of part surfaces. Their basic form is a triangle, with the tip pointing vertically from outside the material to the surface being marked. The tip of the symbol must point vertically from outside the material to the surface being marked, and the marking can be placed on dimension lines, contour lines, extension lines, or in codes. In this article, we will implement the roughness marking function through secondary development of MxCAD by analyzing surface roughness symbols.
Analysis of Surface Roughness Symbols
- Basic Symbols: These symbols indicate that the surface can be obtained by any method. They are concise and intuitive, serving as the basis for expressing design intent.
- Processing Method Symbols: Adding a horizontal line to the long side of the basic symbol allows marking of relevant processing methods. Whether it is turning, milling, or any other material removal method, it can be clearly expressed through these symbols.
- Material Removal Method Symbols: Adding a small circle to the basic symbol indicates that the surface is obtained by material removal methods. This can include not only traditional processing methods such as turning and milling but also more refined treatments like grinding and polishing.
- Same Material Removal Method Symbols: If multiple surfaces have the same material removal method requirements, a small circle can be added to the basic symbol to indicate that these surfaces have the same roughness requirements.
- As-Supplied Condition Symbols: In some cases, the surface needs to maintain its original supplied condition. At this time, a small circle can be added to the basic symbol to indicate that these surfaces do not require any additional processing.
- Combination of Symbols and Codes: In actual design, we may encounter the combined use of multiple symbols. For example, a basic symbol with a short line and explanatory markings indicates that the surface is obtained by a specific material removal method.
Implementation of Custom Entity
Based on the analysis of roughness symbols in the above content, we can obtain the core data of roughness marking, and implement the roughness marking entity through the custom entity McDbCustomEntity in MxCAD.
Basic Structure Settings
tsexport class McDbTestRoughness extends McDbCustomEntity { // Definition of basic attributes private position: McGePoint3d = new McGePoint3d(); // Marking position private textDownString: string[] = ['1.6']; // Text below private textUpString: string[] = []; // Text above private textLeftString: string = ''; // Text on the left private CornerType: string = ''; // Corner mark type private markType: number = 0; // Marking type private dimSize: number = 5; // Marking size private rotation: number = 0; // Rotation angle private dimHeight: number = 10; // Marking height private isSameRequire: boolean = false; // Whether it is the same requirement private isAddLongLine: boolean = false; // Whether to perform lengthening treatment private isMostSymbols: boolean = false; // Majority symbols // Bounding box private minPt: McGePoint3d = new McGePoint3d(); private maxPt: McGePoint3d = new McGePoint3d(); }Constructor and Creation Method
tsconstructor(imp?: any) { super(imp); } public create(imp: any) { return new McDbTestRoughness(imp); } public getTypeName(): string { return "McDbTestRoughness"; }Data Persistence
ts// Read custom entity data public dwgInFields(filter: IMcDbDwgFiler): boolean { this.position = filter.readPoint("position").val; this.minPt = filter.readPoint("minPt").val; this.maxPt = filter.readPoint("maxPt").val; const textDownStr = filter.readString("textDownStr").val; this.textDownString = textDownStr.split(',').filter((item) => item != "");; const textUpStr = filter.readString("textUpStr").val; this.textUpString = textUpStr.split(',').filter((item) => item != "");; this.textLeftString = filter.readString("textLeftStr").val; this.CornerType = filter.readString('CornerType').val; this.markType = filter.readLong('markType').val; this.dimSize = filter.readDouble('dimSize').val; this.rotation = filter.readDouble('rotation').val; this.dimHeight = filter.readDouble('dimHeight').val; this.isSameRequire = filter.readLong('isSameRequire').val == 1 ? true : false; this.isAddLongLine = filter.readLong('isAddLongLine').val == 1 ? true : false; this.isMostSymbols = filter.readLong('isMostSymbols').val == 1 ? true : false; return true; } // Write custom entity data public dwgOutFields(filter: IMcDbDwgFiler): boolean { filter.writePoint("position", this.position); filter.writePoint("maxPt", this.maxPt); filter.writePoint("minPt", this.minPt); const textDownStr = this.textDownString.join(','); const textUpStr = this.textUpString.join(','); filter.writeString("textDownStr", textDownStr); filter.writeString("textUpStr", textUpStr); filter.writeString("textLeftStr", this.textLeftString); filter.writeString('CornerType', this.CornerType); filter.writeLong('markType', this.markType); filter.writeDouble('dimSize', this.dimSize); filter.writeDouble('rotation', this.rotation); filter.writeDouble('dimHeight', this.dimHeight); filter.writeLong('isSameRequire', this.isSameRequire ? 1 : 0); filter.writeLong('isAddLongLine', this.isAddLongLine ? 1 : 0); filter.writeLong('isMostSymbols', this.isMostSymbols ? 1 : 0); return true; }Set Marking Grips and Grip Movement Rules
ts// Move grip points: the marking point is the grip point, and if the grip point moves, the marking point moves accordingly public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) { this.assertWrite(); this.position.x += dXOffset; this.position.y += dYOffset; this.position.z += dZOffset; } // Get grip points public getGripPoints(): McGePoint3dArray { let ret = new McGePoint3dArray(); ret.append(this.position); return ret; }Draw Marking Entity
ts// Get all entities public getAllEntity(): McDbEntity[] { // Draw roughness shape according to roughness const mxcad = MxCpp.getCurrentMxCAD(); const entityArr = this.drawShape(this.markType, this.position, this.dimSize, true); const pl = entityArr[0] as McDbPolyline; const lastPoint = pl.getPointAt(pl.numVerts() - 1).val; // Add left text if (this.textLeftString) { const textLeft = new McDbText(); textLeft.textString = this.textLeftString; textLeft.height = this.dimSize * (9 / 10); textLeft.alignmentPoint = textLeft.position = this.position.clone().addvec(McGeVector3d.kYAxis.clone().mult(this.dimSize * (1 / 10))).addvec(McGeVector3d.kXAxis.clone().negate().mult(this.dimSize)) textLeft.horizontalMode = McDb.TextHorzMode.kTextRight; entityArr.push(textLeft) } // Add corner mark if (this.CornerType) { const textCorner = new McDbText(); textCorner.textString = this.CornerType; textCorner.height = this.dimSize * (7 / 10); textCorner.alignmentPoint = textCorner.position = this.position.clone().addvec(McGeVector3d.kYAxis.clone().mult(this.dimSize * (3 / 10))).addvec(McGeVector3d.kXAxis.clone().mult(this.dimSize * (9 / 10))) textCorner.horizontalMode = McDb.TextHorzMode.kTextLeft; entityArr.push(textCorner) } // Same requirement if (this.isSameRequire) { const cirlce = new McDbCircle(); cirlce.center = lastPoint.clone(); cirlce.radius = this.dimSize * (3 / 10); entityArr.push(cirlce); } // Lengthen horizontal line let endX = lastPoint.x; // Draw superscript text const height = (7 / 10) * this.dimSize; const basePos = lastPoint.clone().addvec(McGeVector3d.kXAxis.clone().mult(this.dimSize * (1 / 2))).addvec(McGeVector3d.kYAxis.clone().mult(this.dimSize * (1 / 5))) let basePos_x: number = basePos.x; let lineArr: McDbLine[] = [] if (this.textUpString.length === 1) { const text = new McDbText(); text.textString = this.textUpString[0]; text.height = height; text.alignmentPoint = text.position = basePos; entityArr.push(text) const { maxPt } = this.getTextBox(text) if (maxPt.x > endX) endX = maxPt.x; basePos_x = text.position.x; } else if (this.textUpString.length === 2) { const text1 = new McDbText(); text1.textString = this.textUpString[0]; text1.height = height; const pos1 = basePos.clone(); const text2 = new McDbText(); text2.height = height; text2.textString = this.textUpString[1]; const v = lastPoint.sub(this.position).mult(2 / 3) const pos2 = pos1.clone().addvec(v); const lastPoint2 = lastPoint.clone().addvec(v); text1.alignmentPoint = text1.position = new McGePoint3d(pos2.x, pos1.y); text2.alignmentPoint = text2.position = pos2; basePos_x = pos2.x; const res1 = this.getTextBox(text1) const res2 = this.getTextBox(text2) const endPt_x = res1.maxPt.x > res2.maxPt.x ? res1.maxPt.x : res2.maxPt.x; if (endX < endPt_x) endX = endPt_x + height * 0.3; const endPoint2 = new McGePoint3d(endX, lastPoint2.y); const line2 = new McDbLine(lastPoint2.x, lastPoint2.y, lastPoint2.z, endPoint2.x, endPoint2.y, endPoint2.y); const line3 = new McDbLine(lastPoint.x, lastPoint.y, lastPoint.z, lastPoint2.x, lastPoint2.y, lastPoint2.z); lineArr.push(line2); entityArr.push(text2); entityArr.push(text1); entityArr.push(line3); } // Draw subscript text if (this.textDownString.length) { const pos = new McGePoint3d(basePos_x, lastPoint.y); this.textDownString.forEach((str, index) => { const text = new McDbText(); text.textString = str; text.height = height; let v: McGeVector3d = new McGeVector3d() v = McGeVector3d.kYAxis.clone().negate().mult(height * (index + 1 + (1 / 6))); text.alignmentPoint = text.position = pos.clone().addvec(v); entityArr.push(text) const res = this.getTextBox(text) endX = endX < res.maxPt.x ? res.maxPt.x : endX; }); }; if (this.isAddLongLine) { const endPoint = lastPoint.clone().addvec(McGeVector3d.kXAxis.clone().mult(this.dimSize * 2)) const line = new McDbLine(lastPoint.x, lastPoint.y, lastPoint.z, endPoint.x, endPoint.y, endPoint.y); if (endX < endPoint.x) { endX = endPoint.x; entityArr.push(line); } } const endPoint = new McGePoint3d(endX, lastPoint.y) const line = new McDbLine(lastPoint.x, lastPoint.y, lastPoint.z, endPoint.x, endPoint.y, endPoint.y); entityArr.push(line) if (lineArr.length) lineArr.forEach(line => { line.endPoint.x = endX; entityArr.push(line); }) // Majority symbols const drawArc = (params: McGePoint3d[]) => { const arc = new McDbArc(); arc.computeArc(params[0].x, params[0].y, params[1].x, params[1].y, params[2].x, params[2].y); entityArr.push(arc) } if (this.isMostSymbols) { // Draw majority symbols (two arcs + two straight lines) // Two arcs const pt = this.position.clone().addvec(McGeVector3d.kYAxis.clone().mult(this.dimSize)); const basePt = new McGePoint3d(endX, pt.y); const radius = this.dimSize * (7 / 5); const center1 = basePt.clone().addvec(McGeVector3d.kXAxis.clone().mult(this.dimSize * (7 / 5))); const v1 = McGeVector3d.kXAxis.clone().mult(radius); const cirlce1_pt1 = center1.clone().addvec(v1.clone().rotateBy(40 * (Math.PI / 180))); const cirlce1_pt2 = center1.clone().addvec(v1.clone()); const cirlce1_pt3 = center1.clone().addvec(v1.clone().rotateBy(320 * (Math.PI / 180))); drawArc([cirlce1_pt1, cirlce1_pt2, cirlce1_pt3]); const center2 = basePt.clone().addvec(McGeVector3d.kXAxis.clone().mult(this.dimSize * (12 / 5))); const cirlce2_pt1 = center2.clone().addvec(v1.clone().rotateBy(140 * (Math.PI / 180))); const cirlce2_pt2 = center2.clone().addvec(v1.clone().negate()); const cirlce2_pt3 = center2.clone().addvec(v1.clone().rotateBy(220 * (Math.PI / 180))); drawArc([cirlce2_pt1, cirlce2_pt2, cirlce2_pt3]); // Draw two straight lines const point = center1.clone().addvec(center2.sub(center1).mult(1 / 3)).addvec(McGeVector3d.kYAxis.clone().negate().mult(this.dimSize * (4 / 5))); this.drawShape(2, point, this.dimSize * (4 / 5), false).forEach(ent => { entityArr.push(ent) }); }; this.getBox(entityArr); const mat = new McGeMatrix3d(); const _height = this.maxPt.y - this.minPt.y; if (this.dimHeight) { const scale = this.dimHeight / _height; mat.setToScaling(scale, this.position); } else { mat.setToScaling(1, this.position); }; entityArr.forEach(ent => { ent.transformBy(mat); }) return entityArr } // Draw roughness shape according to roughness type private drawShape(markType, position, dimSize, flag): McDbEntity[] { const entityArr: McDbEntity[] = []; const v = McGeVector3d.kYAxis.clone().mult(dimSize); const vec = McGeVector3d.kXAxis.clone().mult(dimSize * (3 / 5)); const midPt = position.clone().addvec(v); const point1 = midPt.clone().addvec(vec); const point2 = midPt.clone().addvec(vec.clone().negate()); let len = dimSize * (2 / 5); if (this.textDownStr.length > 2 && flag) len = dimSize * (1 / 2); const point3 = position.clone().addvec(point1.sub(position).mult(len)); if (markType === 0) { // Original style (inverted triangle) const pl = new McDbPolyline(); pl.addVertexAt(point1); pl.addVertexAt(point2); pl.addVertexAt(position); pl.addVertexAt(point3); entityArr.push(pl) } else { // Unclosed triangle const pl = new McDbPolyline(); pl.addVertexAt(point2); pl.addVertexAt(position); pl.addVertexAt(point3); entityArr.push(pl) } if (markType === 1) { // With circle: inscribed circle of the triangle const a = position.distanceTo(point1); const b = position.distanceTo(point2); const c = point1.distanceTo(point2); const s = (a + b + c) / 2; const h = position.distanceTo(midPt); const A = (c * h) / 2; const circle = new McDbCircle(); circle.radius = A / s; circle.center = midPt.clone().addvec(McGeVector3d.kYAxis.clone().negate().mult(circle.radius)); entityArr.push(circle) } return entityArr } // Draw entity public worldDraw(draw: MxCADWorldDraw): void { const entityArr = this.getAllEntity(); this.getBox(entityArr); entityArr.forEach(item => { const _clone = item.clone() as McDbEntity; if (this.rotation) _clone.rotate(this.position, this.rotation); draw.drawEntity(_clone); }); }Expose Properties or Methods for Getting and Setting Internal Data of the Entity
ts// Get or set the content of the subscript text public set textDownStr(val: string[]) { this.textDownString = val; } public get textDownStr(): string[] { return this.textDownString; } // Get or set the content of the superscript text public set textUpStr(val: string[]) { this.textUpString = val; } public get textUpStr(): string[] { return this.textUpString; } // Get or set the content of the left text public set textLeftStr(val: string) { this.textLeftString = val; } public get textLeftStr(): string { return this.textLeftString; } // Get or set the corner mark type public set rougCornerType(val: string) { this.CornerType = val; } public get rougCornerType(): string { return this.CornerType; } // Get or set the marking type public set rougMarkType(val: number) { this.markType = val; } public get rougMarkType(): number { return this.markType; } // Get or set the same requirement public set isRoungSameRequire(val: boolean) { this.isSameRequire = val; if (val) this.isAddLongLine = true; } public get isRoungSameRequire(): boolean { return this.isSameRequire; } // Get or set lengthening public set isAddRougLongLine(val: boolean) { this.isAddLongLine = val; } public get isAddRougLongLine(): boolean { return this.isAddLongLine; } // Get or set majority symbols public set isShowMostSymbols(val: boolean) { this.isMostSymbols = val; } public get isShowMostSymbols(): boolean { return this.isMostSymbols; } // Get or set the height of the roughness marking public set rouDimHeight(val: number) { this.dimHeight = val; } public get rouDimHeight(): number { return this.dimHeight; } // Set the position of the roughness marking public setPos(pt: McGePoint3d) { this.position = pt.clone(); } // Get the position of the roughness marking public getPos() { return this.position; } // Rotation angle public setRotation(angle: number) { this.rotation = angle } // Rotation angle public getRotation(): number { return this.rotation } // Get bounding box public getBoundingBox(): { minPt: McGePoint3d; maxPt: McGePoint3d; ret: boolean; } { const entityArr = this.getAllEntity(); this.getBox(entityArr); return { minPt: this.minPt, maxPt: this.maxPt, ret: true } }
Using Roughness Marking
According to the characteristic that when roughness is marked on a straight line, arc or circle, the marking will be perpendicular to the tangent line of the curve at the position, we can obtain the tangent direction and position of the entity at the marking point by identifying the entity type where the marking point is located, and determine the rotation angle and direction of the marking based on this.
// Roughness
async function Mx_Roughness() {
const mxcad = MxCpp.getCurrentMxCAD();
let rotation = 0;
const roughness = new McDbTestRoughness();
roughness.rougMarkType = 0;
roughness.rougCornerType = '';
const getPos = new MxCADUiPrPoint();
getPos.setMessage(t('Please set the positioning point or straight line or arc or circle'));
getPos.setUserDraw((pt, pw) => {
roughness.setPos(pt);
pw.drawMcDbEntity(roughness)
});
const pos = await getPos.go();
let filter = new MxCADResbuf([DxfCode.kEntityType, "LINE,ARC,CIRCLE,LWPOLYLINE"]);
let objId = MxCADUtility.findEntAtPoint(pos.x, pos.y, pos.z, -1, filter);
if (objId.isValid()) {
const ent = objId.getMcDbEntity();
// Drag to determine the marking position
const getDirect = new MxCADUiPrPoint();
getDirect.setMessage(t('Drag to determine the marking position'));
getDirect.setUserDraw((pt, pw) => {
const line = ent.clone() as McDbLine;
const closePt = line.getClosestPointTo(pt, true).val;
const v = pt.sub(closePt);
rotation = v.angleTo2(McGeVector3d.kYAxis, McGeVector3d.kNegateZAxis);
roughness.setPos(closePt);
roughness.setRotation(rotation);
pw.drawMcDbEntity(roughness)
});
getDirect.setDisableDynInput(true);
getDirect.disableAllTrace(true);
const pt = await getDirect.go();
if (!pt) return;
mxcad.drawEntity(roughness)
} else {
roughness.setPos(pos);
// Specify rotation direction
const getAngle = new MxCADUiPrAngle();
getAngle.setBasePt(pos);
getAngle.setMessage(t('Please specify or enter the angle'));
getAngle.setUserDraw((pt, pw) => {
const line = new McDbLine(pt, pos);
pw.drawMcDbEntity(line);
const v = pt.sub(pos);
rotation = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);
roughness.setRotation(rotation);
pw.drawMcDbEntity(roughness)
});
let val = await getAngle.go();
if (!val) return;
const angle = getAngle.value();
roughness.setRotation(angle);
mxcad.drawEntity(roughness);
}
}Effect Demonstration
Online demo address: https://www.webcadsdk.com/mxcad/
Basic effect demonstration:

Based on the above content, extended development can be carried out to set up a roughness pop-up box, and its example effect is as follows:

